#include "thread.h"

#include <assert.h>
#include <string.h>
#include <SDL2/SDL_thread.h>

#include "log.h"

// 主线程ID，main线程的ID 在thread.h头文件中已经有定义
sc_thread_id SC_MAIN_THREAD_ID;

bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
                 void *userdata) {
    // The thread name length is limited on some systems. Never use a name
    // longer than 16 bytes (including the final '\0')
    assert(strlen(name) <= 15);

    // 创建一个后台线程，如果创建成功就开始运行
    SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
    if (!sdl_thread) {
        LOG_OOM();
        return false;
    }

    thread->thread = sdl_thread;
    return true;
}

/// @brief 将自定义线程优先级转换为SDL的线程优先级
/// @param 自定义的线程优先级
/// @return SDL官方的线程优先级
static SDL_ThreadPriority
to_sdl_thread_priority(enum sc_thread_priority priority) {
    switch (priority) {
        case SC_THREAD_PRIORITY_TIME_CRITICAL:

        // 跟踪定义可得，这个宏相当于是限制了SDL的最低版本 > 2.0.16
#ifdef SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
            return SDL_THREAD_PRIORITY_TIME_CRITICAL;
#else
            // fall through
#endif
        case SC_THREAD_PRIORITY_HIGH:
            return SDL_THREAD_PRIORITY_HIGH;
        case SC_THREAD_PRIORITY_NORMAL:
            return SDL_THREAD_PRIORITY_NORMAL;
        case SC_THREAD_PRIORITY_LOW:
            return SDL_THREAD_PRIORITY_LOW;
        default:
            assert(!"Unknown thread priority");
            LOGI("Unknown thread priority");
            // 相当于是返回了 SDL_THREAD_PRIORITY_LOW ，查看 SDL_ThreadPriority 枚举定义即可
            return 0;
    }
}

bool
sc_thread_set_priority(enum sc_thread_priority priority) {
    SDL_ThreadPriority sdl_priority = to_sdl_thread_priority(priority);
    int r = SDL_SetThreadPriority(sdl_priority);
    if (r) {
        LOGD("Could not set thread priority: %s", SDL_GetError());
        return false;
    }

    return true;
}

void
sc_thread_join(sc_thread *thread, int *status) {
    /*
    void SDL_WaitThread(SDL_Thread* thread,int* status)
    功能：等待一个线程的完成
    参数：
        thread:等待的线程id
        status:线程的退出状态
    */    
    SDL_WaitThread(thread->thread, status);
}

/*
sdl线程的创建：这里描述这个的主要原因是因为ffplay是这样实现的，
一个线程送解码，一个线程获取解码之后的数据，因为是多线程并且有队列等的管理，所以也使用了锁和信号量

在音视频领域主要是实现音视频同步。实现了音视频同步，我们的播放器就基本上合格了。
锁用于完成互斥；信号量用于同步。

基本介绍

线程：
SDL线程创建：SDL_CreateThread
SDL线程等待：SDL_WaitThead

锁
SDL互斥锁：SDL_CreateMutex/SDL_DestroyMutex
SDL锁定互斥：SDL_LockMutex/SDL_UnlockMutex

信号量：
SDL条件变量(信号量)：SDL_CreateCond/SDL_DestoryCond
SDL条件变量(信号量)等待/通知：SDL_CondWait/SDL_CondSingal

补充：
锁： 一个线程拿到锁，另一个线程必须等锁释放才能获取到锁并将线程继续往下执行
信号量： 当SDL_CondWait等待信号量的时候，线程会将锁释放掉，

参考链接：https://www.cnblogs.com/renhui/p/10498177.html
*/

bool
sc_mutex_init(sc_mutex *mutex) {
    // SDL互斥锁：SDL_CreateMutex/SDL_DestroyMutex
    SDL_mutex *sdl_mutex = SDL_CreateMutex();
    if (!sdl_mutex) {
        LOG_OOM();
        return false;
    }

    mutex->mutex = sdl_mutex;
#ifndef NDEBUG
    // atomic_init 原子类操作，初始化一个数值
    // atomic_init 不是基本操作。这不是线程安全的。
    atomic_init(&mutex->locker, 0);
#endif
    return true;
}

void
sc_mutex_destroy(sc_mutex *mutex) {
    // SDL互斥锁：SDL_CreateMutex/SDL_DestroyMutex
    SDL_DestroyMutex(mutex->mutex);
}

void
sc_mutex_lock(sc_mutex *mutex) {
    // SDL mutexes are recursive, but we don't want to use recursive mutexes
    assert(!sc_mutex_held(mutex));
    int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
    if (r) {
        // TODO 既然是加锁，为什么加锁失败直接退出了？
        LOGE("Could not lock mutex: %s", SDL_GetError());
        abort();
    }

    atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
                          memory_order_relaxed);
#else
    // todo  怀疑与上面的宏展开有关，SDL_UnlockMutex
    (void) r;
#endif
}

void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
    assert(sc_mutex_held(mutex));
    atomic_store_explicit(&mutex->locker, 0, memory_order_relaxed);
#endif
    int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
    if (r) {
        LOGE("Could not lock mutex: %s", SDL_GetError());
        abort();
    }
#else
    (void) r;
#endif
}

sc_thread_id
sc_thread_get_id(void) {
// 返回当前线程id TODO SDL_ThreadID()这个函数和getpid()返回是不同的，为什么    
    return SDL_ThreadID();
}

#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
     // atomic_load_explicit 以原子方式加载并返回指向的原子变量的当前值obj。该操作是原子读取操作。
    sc_thread_id locker_id =
        atomic_load_explicit(&mutex->locker, memory_order_relaxed);
    
     /* 存储的线程id和直接获取的当前的线程id进行比较 */
    return locker_id == sc_thread_get_id();
}
#endif

bool
sc_cond_init(sc_cond *cond) {
    // SDL条件变量(信号量)：SDL_CreateCond/SDL_DestoryCond    
    SDL_cond *sdl_cond = SDL_CreateCond();
    if (!sdl_cond) {
        LOG_OOM();
        return false;
    }

    cond->cond = sdl_cond;
    return true;
}

void
sc_cond_destroy(sc_cond *cond) {
    SDL_DestroyCond(cond->cond);
}

void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
    /*
    释放传递进来的锁，然后当条件变量满足的时候，等待其他的线程调用SDL_CondSignal或者SDL_CondBroadcast
改变条件变量cond,然后重新获取锁，然后返回。在调用该函数之前必须先调用SDL_LockMutex获取
锁。当信号被触发的时候就返回0，错误的时候返回-1

简而言之，就是释放锁，让其他的线程有机会获取到锁，满足条件的时候，其他的线程会调用SDL_CondSignal或者SDL_CondBroadcast
释放锁，并且让当前的线程继续执行下去

SDL_CondWait先检测是否满足条件，若不满足，解锁mutex，wait，
直至被SDL_CondSignal()函数或者SDL_CondBroadcast()函数通知，
则锁定mutex并返回

    SDL_CondWait及SDL_CondSignal常成对的出现
    */    
    int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
    if (r) {
        LOGE("Could not wait on condition: %s", SDL_GetError());
        abort();
    }

    atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
                          memory_order_relaxed);
#else
    (void) r;
#endif
}

bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
    sc_tick now = sc_tick_now();
    if (deadline <= now) {
        return false; // timeout
    }

    // Round up to the next millisecond to guarantee that the deadline is
    // reached when returning due to timeout
    uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1);
    int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
    if (r < 0) {
        LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
        abort();
    }

    atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
                          memory_order_relaxed);
#endif
    assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
    // The deadline is reached on timeout
    assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline);
    return r == 0;
}

void
sc_cond_signal(sc_cond *cond) {
        // 唤醒正在等待条件变量发生改变的线程
    int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
    if (r) {
        LOGE("Could not signal a condition: %s", SDL_GetError());
        abort();
    }
#else
    // todo 这个操作有什么用吗
    (void) r;
#endif
}

void
sc_cond_broadcast(sc_cond *cond) {
    int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
    if (r) {
        LOGE("Could not broadcast a condition: %s", SDL_GetError());
        abort();
    }
#else
    (void) r;
#endif
}
